import service, { IAxiosRequestConfig } from '@/plugins/axios'
import { useCommonStore } from '@/store/common'
import EventEmitter from '@/utils/ObEvent'
import { AxiosResponse } from 'axios'
import { nextTick } from 'vue'

const sendToken = new Map()

class Request {
  static instance: Request | null = null;
  private path: string;
  private config: IAxiosRequestConfig;
  private cache: boolean;
  private obSend: EventEmitter;
  constructor () {
    this.path = ''
    this.config = {}
    this.cache = false
    this.obSend = new EventEmitter()
  }

  static getInstance () {
    if (!Request.instance) {
      Request.instance = new Request()
    }
    return Request.instance
  }

  async withAction<T> (carryData = {}, method: string, dataCallback?: (data: T | unknown) => T): Promise<T> {
    const toMethod = method.toUpperCase()
    const isSendData = ['POST', 'PUT', 'DELETE'].includes(toMethod)
    const url = `${this.path}`.replace(/(\/\/)/g, '/')
    const cacheKey = `${url}${JSON.stringify(carryData)}`
    const isCache = this.cache && toMethod === 'GET'
    return new Promise((resolve) => {
      const commonStore = useCommonStore()
      if (sendToken.get(cacheKey)) {
        this.obSend?.once(cacheKey, resolve as any)
      }
      if (isCache && commonStore.cacheAction.get(cacheKey)) {
        resolve(commonStore.cacheAction.get(cacheKey) as unknown as T)
        return
      }
      // 只处理cache重复请求
      if (isCache && !sendToken.get(cacheKey)) {
        sendToken.set(cacheKey, true)
      }
      service({
        url,
        method,
        [isSendData ? 'data' : 'params']: carryData,
        ...this.config
      }).then((data: AxiosResponse<T>) => {
        if (isCache) {
          // 缓存数据缓存处理后的数据
          const withData = dataCallback ? dataCallback(data) : data
          commonStore.handleSetCacheAction(cacheKey, withData)
          resolve(withData as T)
          return
        }
        resolve(data as unknown as T)
      }).finally(() => {
        const commonStore = useCommonStore()
        const cacheData = commonStore.cacheAction.get(cacheKey)
        if (isCache && sendToken.get(cacheKey) && cacheData) {
          nextTick(() => {
            sendToken.delete(cacheKey)
            this.obSend?.emit(cacheKey, cacheData)
          })
        }
      })
    })
  }

  setPath (url: string, loading = false) {
    this.path = url
    this.config = {}
    this.setConfig({ loading })
    return this
  }

  setConfig = (config: IAxiosRequestConfig) => {
    this.config = Object.assign(this.config, config)
    return this
  }

  carry = (key: string) => {
    this.path = this.path.replace(/\{.*?\}/g, () => key)
    return this
  }

  get = <T>(params?: boolean | object, cache = false, dataCallBack?: (data: T | unknown) => T) => {
    if (params && typeof params === 'boolean') {
      cache = params
      params = {}
    }
    this.cache = cache
    return this.withAction<T>(params, 'get', dataCallBack)
  }

  post = <T> (data: object) => {
    return this.withAction<T>(data, 'post')
  }

  put = <T> (data: object) => {
    return this.withAction<T>(data, 'put')
  }

  del = <T> (params: object) => {
    return this.withAction<T>(params, 'delete')
  }

  upload = <T> (file: File | Blob, data?: { [key: string]: any }) => {
    const formData = new FormData()
    formData.append('file', file)
    Object.keys(data || {}).forEach((key) => {
      formData.append(key, data && data[key])
    })
    this.setConfig(Object.assign(this.config, { headers: { 'Content-Type': 'multipart/form-data' } }))
    return this.withAction<T>(formData, 'post')
  }
}

export default Request.getInstance()
